package experimental;

/**
 this software is under GNU GPL
 IMPORTANT: I'm not proud of this code .D
 USAGE: just configure DATA_RATE, the port, and listo
 */

import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.TooManyListenersException;

public class My3DCube extends Applet implements
Runnable, SerialPortEventListener {
	
	//---------------------------------------------------------------------------//
	//-------------------VARIABLES AND SETUP-------------------------------------//
	//---------------------------------------------------------------------------//

	// for containing the ports that will be found
	private List ports = new ArrayList();
	// map the port names to CommPortIdentifiers
	private HashMap portMap = new HashMap();

	// this is the object that contains the opened port
	private CommPortIdentifier selectedPortIdentifier = null;
	private SerialPort serialPort = null;

	// input and output streams for sending and receiving data
	private InputStream input = null;
	private OutputStream output = null;
	// the timeout value for connecting with the port

	// connection indicator
	private boolean bConnected = false;

	final static int TIMEOUT = 5000;
	final static int DATA_RATE = 9600; //CHANGE IT IF YOU WANT

	final static char X_CHARACTER_SEPARATOR = 'X';
	final static char Y_CHARACTER_SEPARATOR = 'Y';
	final static char Z_CHARACTER_SEPARATOR = 'Z';

	String selectedPort = "";
	String logText = "";

	Thread luxury;

	static Point[] vertex = new Point[8]; // Cube vertexes

	static int maxSize = 300; // screen size

	static int origin = maxSize / 2; // (0,0)

	double xCoord = 0;
	double yCoord = 0;
	double zCoord = 0; // X and Y mouse's positions

	double xAux, yAux, zAux;// old X and Y mouse's positions

	int module = 50; // length of lines of cube.

	Image canvasAux;

	Graphics backBuffer;
	
//---------------------------------------------------------------------------//
//-------------------APPLET HANDLING MOSTLY----------------------------------//
//---------------------------------------------------------------------------//

	public void init() {

		setSize(maxSize, maxSize);

		setBackground(new Color(0.2f, 0.6f, 0.1f, 1.0f));

		/*
		 * Initial coordinates of eight vertex module is half the length of the
		 * cube
		 */

		int[] coordX = new int[] { -module, module, module, -module, -module,
				module, module, -module };

		int[] coordY = new int[] { -module, -module, module, module, -module,
				-module, module, module };

		int[] coordZ = new int[] { module, module, module, module, -module,
				-module, -module, -module };

		canvasAux = createImage(500, 500);

		backBuffer = canvasAux.getGraphics();

		for (int i = 0; i < vertex.length; i++) {

			vertex[i] = new Point(coordX[i], coordY[i], coordZ[i]);

		}

		connect();
		searchForPorts();
		initIOStream();

		if (getConnected() == true) {
			if (initIOStream() == true) {
				initListener();
			}
		}

	}

	public void start() {

		try {
			luxury = new Thread(this);
			luxury.start();
		}

		catch (Exception e) {
		}

	}

	public void run() {
	}

	public void stop() {
	}

	public void paint(Graphics g) {

		/*
		 * Cube's points. Requires four points to create a face, requires six
		 * faces to create a cube.
		 */

		int[] pts1 = new int[] { 0, 1, 5, 0, 0, 3 };

		int[] pts2 = new int[] { 1, 5, 4, 3, 4, 2 };

		int[] pts3 = new int[] { 2, 6, 7, 7, 5, 6 };

		int[] pts4 = new int[] { 3, 2, 6, 4, 1, 7 };

		Color[] colorRGB = new Color[] { Color.orange, Color.blue, Color.black,

				Color.pink, Color.red, Color.yellow };

		Face[] faces = new Face[6];

		backBuffer.clearRect(0, 0, 500, 500);

		for (int i = 0; i < 6; i++) {

			backBuffer.setColor(colorRGB[i]);

			faces[i] = new

					Face(vertex[pts1[i]], vertex[pts2[i]], vertex[pts3[i]],
							vertex[pts4[i]]);

			if (faces[i].isVisible()) {

				faces[i].drawFace(backBuffer);

			}

		}

		g.drawImage(canvasAux, 0, 0, this);

		g.drawString(" Touche ", 30, 30);

		g.drawString(" X " + xCoord, 30, 50);

		g.drawString(" Y " + yCoord, 30, 65);

		return;

	}

	public void update(Graphics g) {
		paint(g);
	}

	public void destroy() {

		try {
			Thread.sleep(1500);
		}

		catch (InterruptedException e) {

			System.out.println("Exception in sleep");

		}

	}

	/* Rotate each vertex of cube over X, Y and Z axis */

	void rotate(double angleTeta, double anglePhi, double anglePsi) {

		double teta = Math.toRadians(angleTeta);

		double phi = Math.toRadians(anglePhi);

		double psi = Math.toRadians(anglePsi);

		Point pAux = new Point();

		Point pAux1 = new Point();

		Point pAux2 = new Point();

		for (int i = 0; i < 8; i++) {

			/* Rotating over x */

			pAux1.x = vertex[i].x;

			pAux1.y = vertex[i].y * Math.cos(teta) + vertex[i].z
					* (-Math.sin(teta));

			pAux1.z = vertex[i].y * Math.sin(teta) + vertex[i].z
					* Math.cos(teta);

			/* Rotating over y */

			pAux2.x = pAux1.x * Math.cos(phi) + pAux1.z * Math.sin(phi);

			pAux2.y = pAux1.y;

			pAux2.z = pAux1.x * (-Math.sin(phi)) + pAux1.z * Math.cos(phi);

			/* Rotating over z */

			pAux.x = pAux2.x * Math.cos(psi) + pAux2.y * (-Math.sin(psi));

			pAux.y = pAux2.x * Math.sin(psi) + pAux2.y * Math.cos(psi);

			pAux.z = pAux2.z;

			/* new position */

			vertex[i].x = pAux.x;

			vertex[i].y = pAux.y;

			vertex[i].z = pAux.z;

		}

	}

	//---------------------------------------------------------------------------//
	//-------------------SERIAL STUFF MOSTLY-------------------------------------//
	//---------------------------------------------------------------------------//

	private void fillPortList(){
		Enumeration portsIds = CommPortIdentifier.getPortIdentifiers();

		while (portsIds.hasMoreElements())
		{
			CommPortIdentifier curPort = (CommPortIdentifier)portsIds.nextElement();
			if (curPort.getPortType() == CommPortIdentifier.PORT_SERIAL) {
				this.ports.add(curPort.getName());
				this.portMap.put(curPort.getName(), curPort);


				System.out.println(curPort.getName() + "is AVAILABLE");
			}
		}
	}
	public List getPorts() {
		return this.ports;
	}


	//pre: ports are already found by using the searchForPorts method
	public void connect()
	{
		this.selectedPort = "/dev/ttyUSB0"; //Manual setting, but automatic setting modification is trivial

		try
		{
			selectedPortIdentifier = CommPortIdentifier.getPortIdentifier(this.selectedPort);
			//the CommPort object can be casted to a SerialPort object
			serialPort = (SerialPort)selectedPortIdentifier.open(this.getClass().getName(), TIMEOUT);
			setConnected(true);
			serialPort.setSerialPortParams(DATA_RATE, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
			//logging
			logText = this.selectedPort + " opened successfully.";
			System.out.println(logText);
			// Wait for the Arduino to set up
			Thread.sleep(2000);
		}
		catch (PortInUseException e)
		{
			logText = selectedPort + " is in use. (" + e.toString() + ")";
			System.out.println(logText);

		}
		catch (Exception e)
		{
			logText = "Failed to open " + this.selectedPort + "(" + e.toString() + ")";
			System.out.println(logText);

		}
	}

	public void setConnected(boolean bConnected) {
		this.bConnected = bConnected;
	}

	public void searchForPorts()
	{
		String fp = File.pathSeparator;
		String tmpl = "/dev/ttyACM0";
		tmpl += fp + "/dev/ttyUSB0";
		tmpl += fp + "/dev/ttyUSB1";
		tmpl += fp + "/dev/ttyACM1";

		System.clearProperty("gnu.io.rxtx.SerialPorts");
		fillPortList();
		//Ignora los puertos del SO si hacemos esto antes:
		System.setProperty("gnu.io.rxtx.SerialPorts", tmpl);
		fillPortList();
	}

	public boolean initIOStream()
	{
		//return value for whether opening the streams is successful or not
		boolean successful = false;

		try {
			input = serialPort.getInputStream();
			output = serialPort.getOutputStream();

			successful = true;
			return successful;
		}
		catch (IOException e) {
			logText = "I/O Streams failed to open. (" + e.toString() + ")";
			System.out.println(logText);

			return successful;
		}
	}
	

	final public boolean getConnected()
	{
		return bConnected;
	}

	//starts the event listener that knows whenever data is available to be read
	//pre: an open serial port
	//post: an event listener for the serial port that knows when data is recieved
	public void initListener()
	{
		if(serialPort == null){
			return;
		}

		try
		{
			serialPort.addEventListener(this);
			serialPort.notifyOnDataAvailable(true);
		}
		catch (TooManyListenersException e)
		{
			logText = "Too many listeners. (" + e.toString() + ")";
			System.out.println(logText);
		}
	}


	//-------------SERIAL EVENT HANDLING-----------------------//

	@Override
	public void serialEvent(SerialPortEvent arg0) {
		/* old coordinates */
		xAux = xCoord;
		yAux = yCoord;
		zAux = zCoord;
		{
			try {
				ArrayList<Character> characters = new ArrayList<Character>();
				char caracter = 0;
				while(caracter != '\n')
				{caracter = (char) input.read(); characters.add(caracter);}

				if(!(characters.size()==1)){
					char actualSeparator = (Character) characters.get(0);
					double value = toDouble(characters);

					/* new coordinates */
					switch (actualSeparator) {
					case X_CHARACTER_SEPARATOR: xCoord = value ;break;
					case Y_CHARACTER_SEPARATOR: yCoord = value ;break;
					case Z_CHARACTER_SEPARATOR: zCoord = value ;break; //Not used; just x-y rotation
					default: break;
					}


					if (xCoord != xAux) {rotateX(xAux-xCoord);}

					if (yCoord != yAux) {rotateY(yAux-yCoord);}



					repaint();}


			}
			catch (Exception e) {
				logText = "Failed to read data. (" + e.toString() + ")";
				System.out.println(logText);
			}
		}

	}
	

	//Maybe this 2 are kinda repetitive, but it s clearer when you see the usage
	public void rotateX(double degrees){
		rotate(-degrees, 0, 0);
	}

	public void rotateY(double degrees){
		rotate(0,degrees, 0);
	}

	
	//---------------------------------------------------------------------------//
	//-------------------HELPER METHODS------------------------------------------//
	//---------------------------------------------------------------------------//


	public double toDouble(List<Character> chars){
		StringBuilder stringBuilder = new StringBuilder(chars.size());

		for(Character ch: chars)
		{
			stringBuilder.append(ch);
		}

		String quasiReturn = stringBuilder.toString();
		quasiReturn = quasiReturn.substring(1);
		return Double.parseDouble(quasiReturn);

	}

}